using System;
using System.Collections.Generic;
using System.IO;
using System.Collections;
using SharpDBM.helper;
using System.Net;

///
// * jdbm LICENSE v1.00
// *
// * Redistribution and use of this software and associated documentation
// * ("Software"), with or without modification, are permitted provided
// * that the following conditions are met:
// *
// * 1. Redistributions of source code must retain copyright
// *    statements and notices.  Redistributions must also contain a
// *    copy of this document.
// *
// * 2. Redistributions in binary form must reproduce the
// *    above copyright notice, this list of conditions and the
// *    following disclaimer in the documentation and/or other
// *    materials provided with the distribution.
// *
// * 3. The name "jdbm" must not be used to endorse or promote
// *    products derived from this Software without prior written
// *    permission of Cees de Groot.  For written permission,
// *    please contact cg@cdegroot.com.
// *
// * 4. Products derived from this Software may not be called "jdbm"
// *    nor may "jdbm" appear in their names without prior written
// *    permission of Cees de Groot.
// *
// * 5. Due credit should be given to the jdbm Project
// *    (http://jdbm.sourceforge.net/).
// *
// * THIS SOFTWARE IS PROVIDED BY THE ndbm PROJECT AND CONTRIBUTORS
// * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
// * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
// * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// * OF THE POSSIBILITY OF SUCH DAMAGE.
// *
// * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
// * Contributions are Copyright (C) 2000 by their associated contributors.
// *
// * $Id: RecordFile.java,v 1.6 2005/06/25 23:12:32 doomdark Exp $
// 

//*************************************************************************
//**  Included in JDMB 1.0 port to SharpDBM;  11/2013 Cyrus Neah cneah@codingreal.com
//**  SharpDBM is an independent reimplementation of the JDBM 1.0 software library in C#  
//*************************************************************************

namespace SharpDBM.recman
{


///
// *  This class represents a random access file as a set of fixed size
// *  records. Each record has a physical record number, and records are
// *  cached in order to improve access.
// *<p>
// *  The set of dirty records on the in-use list constitutes a transaction.
// *  Later on, we will send these records to some recovery thingy.
// 
	public   class RecordFile 
	{
		internal readonly TransactionManager txnMgr;
		private bool disposed = false;

	// Todo: reorganize in hashes and fifos as necessary.
	// free -> inUse -> dirty -> inTxn -> free
	// free is a cache, thus a FIFO. The rest are hashes.

		private readonly LinkedList<BlockIo> free = new LinkedList<BlockIo>();
		private readonly Dictionary<long, BlockIo> inUse = new Dictionary<long,BlockIo>();
		private readonly Dictionary<long, BlockIo> dirty = new Dictionary<long, BlockIo>();
		private readonly Dictionary<long, BlockIo> inTxn = new Dictionary<long, BlockIo>();

	// transactions disabled?
		private bool transactionsDisabled = false;

	/// The length of a single block. 
		public const int BLOCK_SIZE = 8192; //4096;

	/// The extension of a record file 
		internal const string extension = ".db";

	/// A block of clean data to wipe clean pages. 
		internal static readonly sbyte[] cleanData = new sbyte[BLOCK_SIZE];

		//private RandomAccessFile file;
		private FileStream file;
		

		private readonly string fileName;

//    *
//     *  Creates a new object on the indicated filename. The file is
//     *  opened in read/write mode.
//     *
//     *  @param fileName the name of the file to open or create, without
//     *         an extension.
//     *  @throws IOException whenever the creation of the underlying
//     *          RandomAccessFile throws it.
//     

		public RecordFile(string fileName)
		{
			
			this.fileName = fileName;//this should be open or create mode
			file = new FileStream(fileName + extension, FileMode.OpenOrCreate, FileAccess.ReadWrite,FileShare.ReadWrite,1024,FileOptions.RandomAccess);

			txnMgr = new TransactionManager(this);
			 
		}


//    *
//     *  Returns the file name.
//     
		public virtual string getFileName()
		{
			return fileName;
		}

//    *
//     *  Disables transactions: doesn't sync and doesn't use the
//     *  transaction manager.
//     
		internal virtual void disableTransactions()
		{
			transactionsDisabled = true;
		}

//    *
//     *  Gets a block from the file. The returned byte array is
//     *  the in-memory copy of the record, and thus can be written
//     *  (and subsequently released with a dirty flag in order to
//     *  write the block back).
//     *
//     *  @param blockid The record number to retrieve.
//     

		 internal virtual BlockIo @get(long blockid)
		 {
			 long key = (blockid);

             

		 // try in transaction list, dirty list, free list

			 BlockIo node;
			 if (inTxn.TryGetValue(key, out node))
			 {

				 node = (BlockIo)node;
			 }
			 
			 if (node != null)
			 {
				 inTxn.Remove(key);
				 inUse.Add(key, node);
				 return node;
			 }
			 if (dirty.TryGetValue(key, out node))
			 {
				 node = (BlockIo)node;
			 }
			 

			 if (node != null)
			 {
				 dirty.Remove(key);
				 inUse.Add(key, node);
				 return node;
			 }
			 
			 IEnumerator<BlockIo> i = free.GetEnumerator();
			 while ( i.MoveNext())
			 {
				 
				 BlockIo cur = (BlockIo)i.Current ;
				 if (cur.getBlockId() == blockid)
				 {
					 node = cur;
					 free.Remove(cur);
					 inUse.Add(key, node);
					 return node;
				 }
			 }

			 // sanity check: can't be on in use list if (dirty.TryGetValue(key, out node))
			 BlockIo sanitycheckNode;
			 if (inUse.TryGetValue(key, out sanitycheckNode))
			 {
				 throw new Error("double get for block " + blockid);
			 }

		 // get a new node and read it from the file
			 node = getNewNode(blockid);
			 long offset = blockid * BLOCK_SIZE;
			 if (file.Length > 0 && offset <= file.Length)
			 {
                 sbyte[] data = node.getData();
                 read(file, offset, ref data, BLOCK_SIZE);
                 node.setData(data);
			 }
			 else
			 {
				 System.Array.Copy(cleanData, 0, node.getData(), 0, BLOCK_SIZE);
			 }
			 inUse.Add(key, node);
			 node.setClean();
			 return node;
		 }


//    *
//     *  Releases a block.
//     *
//     *  @param blockid The record number to release.
//     *  @param isDirty If true, the block was modified since the get().
//     

		internal virtual void release(long blockid, bool isDirty)
		{
			
			 BlockIo node;
			 if (inUse.TryGetValue(blockid, out node))
			 {
				 //node = (BlockIo)inUse[blockid];
				 node = (BlockIo)node;
			 }



			if (node == null)
				throw new IOException("bad blockid " + blockid + " on release");
			if (!node.isDirty() && isDirty)
				node.setDirty();
			release(node);
		}

//    *
//     *  Releases a block.
//     *
//     *  @param block The block to release.
//     
		internal virtual void release(BlockIo block)
		{
			long key = block.getBlockId();
			inUse.Remove(key);
			if (block.isDirty())
			{
			// System.out.println( "Dirty: " + key + block );
				dirty.Add(key, block);
			}
			else
			{
				if (!transactionsDisabled && block.isInTransaction())
				{
					inTxn.Add(key, block);
				}
				else
				{
					free.AddLast(block);
				}
			}
		}

//    *
//     *  Discards a block (will not write the block even if it's dirty)
//     *
//     *  @param block The block to discard.
//     
		internal virtual void discard(BlockIo block)
		{
			long key = block.getBlockId();
			inUse.Remove(key);

		// note: block not added to free list on purpose, because
		//       it's considered invalid
		}

//    *
//     *  Commits the current transaction by flushing all dirty buffers
//     *  to disk.
//     

		internal virtual void commit()
		{
		// debugging...
			if (inUse.Count != 0 && inUse.Count > 1)
			{
				//showList(inUse.values().iterator());
				showList(inUse.Values.GetEnumerator());
				throw new Exception("in use list not empty at commit time (" + inUse.Count + ")");
			}

		//  System.out.println("committing...");

			if (dirty.Count == 0)
			{
			// if no dirty blocks, skip commit process
				return;
			}

			if (!transactionsDisabled)
			{
				txnMgr.start();
			}

			
			IEnumerator<BlockIo> i = dirty.Values.GetEnumerator();            

			while (i.MoveNext())
			{
				
				BlockIo node = (BlockIo)i.Current;
				//i.Remove();
				//dirty.Remove(node.getBlockId());c# comment not possible in c# clear at the end
			// System.out.println("node " + node + " map size now " + dirty.size());
				if (transactionsDisabled)
				{
					long offset = node.getBlockId() * BLOCK_SIZE;
					file.Seek(offset,0);
					byte[] bytes = Array.ConvertAll(node.getData(), b => (byte)b);
					file.Write(bytes, 0, bytes.Length);
					node.setClean();
					free.AddLast(node);
				}
				else
				{
					txnMgr.add(node);
					inTxn.Add(node.getBlockId(), node);
				}
			}
			

				if (!transactionsDisabled)
				{
					txnMgr.commit();
				}
				dirty.Clear();//remove all elements here

		}

//    *
//     *  Rollback the current transaction by discarding all dirty buffers
//     

		internal virtual void rollback()
		{
		// debugging...
			if (inUse.Count != 0)
			{
				showList(inUse.Values.GetEnumerator());
				throw new Exception("in use list not empty at rollback time (" + inUse.Count + ")");
			}
		//  System.out.println("rollback...");
			dirty.Clear();

			txnMgr.synchronizeLogFromDisk();

			if (inTxn.Count != 0)
			{
				showList(inTxn.Values.GetEnumerator());
				throw new Exception("in txn list not empty at rollback time (" + inTxn.Count + ")");
			}
		}

//    *
//     *  Commits and closes file.
//     

		internal virtual void close()
		{
			if (dirty.Count != 0)
			{
				commit();
			}
			txnMgr.synchronizeLogFromDisk();
			txnMgr.shutdown();
			
			if (inTxn.Count != 0)
			{
				
				showList(inTxn.Values.GetEnumerator());
				throw new Exception("In transaction not empty");
			}

		// these actually ain't that bad in a production release
			if (dirty.Count != 0)
			{
				Console.WriteLine("ERROR: dirty blocks at close time");
				showList(dirty.Values.GetEnumerator());
				throw new Exception("Dirty blocks at close time");
			}
			if (inUse.Count != 0)
			{
				Console.WriteLine("ERROR: inUse blocks at close time");
				showList(inUse.Values.GetEnumerator());
				throw new Exception("inUse blocks at close time");
			}

		// debugging stuff to keep an eye on the free list
		// System.out.println("Free list size:" + free.size());
			file.Close();
            Dispose();
            file = null;
		}


//    *
//     * Force closing the file and underlying transaction manager.
//     * Used for testing purposed only.
//     

		internal virtual void forceClose()
		{
		  txnMgr.forceClose();
		  file.Close();
		}

//    *
//     *  Prints contents of a list
//     
		private void showList(IEnumerator<BlockIo> i)
		{
			int cnt = 0;
			while (i.MoveNext())
			{
				Console.WriteLine("elem " + cnt + ": " + i.MoveNext());
				cnt++;
			}
		}


//    *
//     *  Returns a new node. The node is retrieved (and removed)
//     *  from the released list or created new.
//     

		private BlockIo getNewNode(long blockid)
		{

			BlockIo retval = null;
			if (free.Count !=0)
			{
				//retval = (BlockIo) free.RemoveFirst();
				retval = (BlockIo)free.First.Value;
				free.RemoveFirst();
			}
			if (retval == null)
				retval = new BlockIo(0, new sbyte[BLOCK_SIZE]);

			retval.setBlockId(blockid);
			retval.setView(null);
			return retval;
		}

//    *
//     *  Synchs a node to disk. This is called by the transaction manager's
//     *  synchronization code.
//     

		internal virtual void synch(BlockIo node)
		{
			sbyte[] data = node.getData();
			if (data != null)
			{
				long offset = node.getBlockId() * BLOCK_SIZE;
				file.Seek(offset, SeekOrigin.Begin);
				byte[] unsigned = Array.ConvertAll(data, b => (byte)b);
				file.Write(unsigned, 0, unsigned.Length);
			}
		}

//    *
//     *  Releases a node from the transaction list, if it was sitting
//     *  there.
//     *
//     *  @param recycle true if block data can be reused
//     

		internal virtual void releaseFromTransaction(BlockIo node, bool recycle)
		{
			long key = node.getBlockId();
			if ((inTxn.Remove(key) != false) && recycle)
			{
				free.AddLast(node);
			}
		}

//    *
//     *  Synchronizes the file.
//     

		internal virtual void sync()
		{
			//file.getFD().sync();
			file.Flush(true);
		}


//    *
//     * Utility method: Read a block from a RandomAccessFile
//     
		private static void read(FileStream file, long offset,ref sbyte[] buffer, int nBytes)
		{
			file.Seek(offset,SeekOrigin.Begin);
			int remaining = nBytes;
            
			byte[] unsigned = Array.ConvertAll(buffer, b => (byte)b);
			int pos = 0;
            
		
			while (remaining > 0)
			{
				
                int read = file.Read(unsigned, pos, remaining);
				if (read == -1)
				{
					System.Array.Copy(cleanData, 0, buffer, pos, remaining);
					break;
				}

				remaining -= read;
                if (remaining == nBytes) { break; }
				pos += read;
			}
            buffer = Array.ConvertAll(unsigned, b => (sbyte)b);
		}
		public void Dispose()
	{
		Dispose(true);
		GC.SuppressFinalize(this);
	}

	protected virtual void Dispose(bool disposing)
	{
		if (!disposed)
		{
			if (disposing)
			{
				// Free other state (managed objects).
			}
			// Free your own state (unmanaged objects).
			// Set large fields to null.

			if (file != null) {  
			file.Close();
			file = null;
		}
			

			disposed = true;
		}
	}

	// Use C# destructor syntax for finalization code.
	~RecordFile()
	{
		// Simply call Dispose(false).
		Dispose (false);
	}

	}

}